﻿using System;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Microsoft.VisualStudio.Shell;
using Microsoft.WowAddonStudio.FrameXml.Components.Support;
using Microsoft.WowAddonStudio.FrameXml.Editor.Support;
using Microsoft.WowAddonStudio.FrameXml.Editor.ToolWindows;
using Microsoft.WowAddonStudio.FrameXml.Serialization.Support;
using Microsoft.WowAddonStudio.FrameXml.Services;
using Microsoft.WowAddonStudio.Services;

namespace Microsoft.WowAddonStudio.FrameXml.Editor
{
	/// <summary>
	/// This is the class that implements the package exposed by this assembly.
	///
	/// The minimum requirement for a class to be considered a valid package for Visual Studio
	/// is to implement the IVsPackage interface and register itself with the shell.
	/// This package uses the helper classes defined inside the Managed Package Framework (MPF)
	/// to do it: it derives from the Package class that provides the implementation of the 
	/// IVsPackage interface and uses the registration attributes defined in the framework to 
	/// register itself and its components with the shell.
	/// </summary>
	// This attribute tells the registration utility (regpkg.exe) that this class needs
	// to be registered as package.
	[PackageRegistration(UseManagedResourcesOnly = true)]
	// A Visual Studio component can be registered under different registry roots; for instance
	// when you debug your package you want to register it in the experimental hive. This
	// attribute specifies the registry root to use if no one is provided to regpkg.exe with
	// the /root switch.
	[DefaultRegistryRoot(@"Software\Microsoft\AppEnv\9.0\Apps\WowAddonStudio")]
	// This attribute registers a tool window exposed by this package.
	// It will initially be docked at the toolbox window location.
    [ProvideToolWindow(typeof(LayoutControllerToolWindow), Style = VsDockStyle.Linked, Orientation = ToolWindowOrientation.Bottom, Window = "B1E99781-AB81-11D0-B683-00AA00A3EE26")]
    [ProvideToolWindowVisibility(typeof(LayoutControllerToolWindow), "f1536ef8-92ec-443c-9ed7-fdadf150da82" /*UICONTEXT_SolutionExists*/)]

    // In order be loaded inside Visual Studio in a machine that does not have the VS SDK installed, 
    // package needs to have a valid load key (it can be requested at 
    // http://msdn.microsoft.com/vstudio/extend/). This attributes tells the shell that this 
    // package has a load key embedded in its resources.
    [ProvideLoadKey("Standard", "1.0", "FrameXML Editor", "WowAddonStudio", 102)]
	[ProvideEditorExtension(typeof (FrameXmlEditorFactory), ".xml", 50, DefaultName = "FrameXML Editor")]
	[ProvideEditorLogicalView(typeof (FrameXmlEditorFactory), "{7651a702-06e5-11d1-8ebd-00a0c90f26ea}")] // LOGVIEWID_Designer
	[ProvideEditorLogicalView(typeof (FrameXmlEditorFactory), "{7651a701-06e5-11d1-8ebd-00a0c90f26ea}")] // LOGVIEWID_Code
	[Guid(GuidStrings.FrameXmlEditorPackage)]
	public class FrameXmlEditorPackage : Package, IDisposable
	{
		private const string frameXmlDirectory = @"Interface\FrameXML";
		private const string tocFileName = "FrameXML.toc";

		private LayoutControllerToolWindow controllerToolWindow;
		private ILayoutControlManager layoutControlManager;

		private ResourceFileLoader resourceFileLoader;
		private FrameXmlImageService imageService;
		private FrameXmlFontService fontService;

		/// <summary>
		/// Initialization of the package; this method is called right after the package is sited, so this is the place
		/// where you can put all the initilaization code that rely on services provided by VisualStudio.
		/// </summary>
		protected override void Initialize()
		{
			base.Initialize();

			IServiceContainer serviceContainer = this;

			IEventBindingService eventService = new FrameXmlEventBindingService(this);
			serviceContainer.AddService(typeof (IEventBindingService), eventService);

			InitializeFrameXmlServices();
			InitializeFrameXmlDirectory();

			RegisterEditorFactory(new FrameXmlEditorFactory(this));

			serviceContainer.AddService(typeof (ILayoutControllerService), OnCreateDesignerHostService, true);

			InitializeLayoutControlToolWindow();
			serviceContainer.AddService(typeof (LayoutControllerToolWindow), controllerToolWindow, true);

			//create LayoutControlManager instance
			layoutControlManager = new LayoutControlManager(this);
			serviceContainer.AddService(typeof (ILayoutControlManager), layoutControlManager);

			// Hook designer event services
			var designerEventService = (IDesignerEventService) GetService(typeof (IDesignerEventService));
			designerEventService.ActiveDesignerChanged += OnActiveDesignerChanged;
		}

		/// <summary>
		/// Uninitializes the frame XML services.
		/// </summary>
		private void UninitializeFrameXmlServices()
		{
			((IServiceContainer) this).RemoveService(typeof (ResourceFileLoader));
			((IServiceContainer) this).RemoveService(typeof (FrameXmlImageService));
			((IServiceContainer) this).RemoveService(typeof (FrameXmlFontService));
		}

		/// <summary>
		/// Initializes the frame XML services.
		/// </summary>
		private void InitializeFrameXmlServices()
		{
			resourceFileLoader = ResourceFileLoaderFactory.CreateResourceFileLoader(this);

			imageService = new FrameXmlImageService(resourceFileLoader);
			fontService = new FrameXmlFontService(resourceFileLoader);

			// Add FrameXML Image and Font services
			AddService(resourceFileLoader);
			AddService(imageService);
			AddService(fontService);
		}

		/// <summary>
		/// Initializes the frame XML directory.
		/// </summary>
		private void InitializeFrameXmlDirectory()
		{
			if (resourceFileLoader != null)
			{
				string tocFilePath = Path.Combine(frameXmlDirectory, tocFileName);
				if (resourceFileLoader.ResourceFileExists(tocFilePath))
				{
					using (var tocFileStream = resourceFileLoader.GetResourceFileStream(tocFilePath))
					{
						foreach (var frameXmlFile in FrameXmlDirectory.GetFrameXmlFiles(tocFileStream))
                            FrameXmlDirectory.RegisterFrameXmlFile(frameXmlFile, file => resourceFileLoader.GetResourceFileStream(Path.Combine(frameXmlDirectory, file)));
					}
				}
			}
		}

		/// <summary>
		/// Callback function for creation <see cref="ILayoutControllerService"/> service instance.
		/// </summary>
		/// <param name="container">The <see cref="IServiceContainer"/> parameter.</param>
		/// <param name="serviceType">The <see cref="Type"/> of the service.</param>
		/// <returns><see cref="ILuaRuntimeService"/> implementation.</returns>
		private ILayoutControllerService OnCreateDesignerHostService(IServiceContainer container, Type serviceType)
		{
			if (serviceType == typeof (ILayoutControllerService))
			{
				var service = container.GetService(typeof (ILayoutControllerService))
				              as ILayoutControllerService ?? new LayoutControllerService(this);
				return service;
			}
			return null;
		}

		/// <summary>
		/// Initialize and register the <see cref="LayoutControllerToolWindow"/> instance.
		/// </summary>
		private void InitializeLayoutControlToolWindow()
		{
			controllerToolWindow = (LayoutControllerToolWindow) FindToolWindow(
				typeof (LayoutControllerToolWindow), 0, true);
		}


		/// <summary>
		/// Creates the specified instance by type.
		/// </summary>
		/// <typeparam name="TInterface">The type of the interface.</typeparam>
		/// <typeparam name="TClass">The type of the class.</typeparam>
		/// <returns></returns>
		public TInterface CreateInstance<TInterface, TClass>()
			where TInterface : class
			where TClass : class
		{
			Guid clsid = typeof (TClass).GUID;
			Guid riid = typeof (TInterface).GUID;

			return (TInterface) CreateInstance(ref clsid, ref riid, typeof (TInterface));
		}

		/// <summary>
		/// Adds the service.
		/// </summary>
		/// <typeparam name="TService">The type of the service.</typeparam>
		/// <param name="serviceInstance">The service instance.</param>
		private void AddService<TService>(TService serviceInstance)
		{
			AddService(typeof (TService), serviceInstance);
		}

		/// <summary>
		/// Adds the specified service to the service container.
		/// </summary>
		/// <param name="serviceType">The type of service to add.</param>
		/// <param name="serviceInstance">An instance of the service type to add. This object must implement or inherit from the type indicated by the <paramref name="serviceType"/> parameter.</param>
		private void AddService(Type serviceType, object serviceInstance)
		{
			((IServiceContainer) this).AddService(serviceType, serviceInstance, true);
		}

		/// <summary>
		/// Gets the service.
		/// </summary>
		/// <typeparam name="TService">The type of the service.</typeparam>
		/// <returns></returns>
		public TService GetService<TService>()
		{
			return (TService) GetService(typeof (TService));
		}

		/// <summary>
		/// Shows the message.
		/// </summary>
		/// <param name="message">The message.</param>
		private static void ShowMessage(string message)
		{
			//IVsUIShell shell = GetService<IVsUIShell>();
			MessageBox.Show(message, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.Warning);
		}

		/// <summary>
		/// Called when [Active designer changed].
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="System.ComponentModel.Design.ActiveDesignerEventArgs"/> instance containing the event data.</param>
		private void OnActiveDesignerChanged(object sender, ActiveDesignerEventArgs e)
		{
			try
			{
				if (e.NewDesigner != null)
				{
					layoutControlManager.Register(e.NewDesigner);
					if (!controllerToolWindow.IsVisible)
					{
						var service = (ILayoutControllerService) GetService(typeof (ILayoutControllerService));
						service.ShowLayoutControl();
					}
				}
				else
				{
					layoutControlManager.Unregister(e.OldDesigner);
				}
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex);
			}
		}

		/// <summary>
		/// Changes the designer resource services.
		/// </summary>
		private void ChangeDesignerResourceServices()
		{
			var designerEventService = (IDesignerEventService) GetService(typeof (IDesignerEventService));
			
			if (designerEventService == null)
				throw new ArgumentException(string.Format(Resources.ServiceMissingMessage, "IDesignerEventService"));

			if (designerEventService.ActiveDesigner != null)
			{
				designerEventService.ActiveDesigner.RemoveService(typeof (ResourceFileLoader));
				designerEventService.ActiveDesigner.RemoveService(typeof (FrameXmlImageService));
				designerEventService.ActiveDesigner.RemoveService(typeof (FrameXmlFontService));

				designerEventService.ActiveDesigner.AddService(typeof (ResourceFileLoader), resourceFileLoader);
				designerEventService.ActiveDesigner.AddService(typeof (FrameXmlImageService), imageService);
				designerEventService.ActiveDesigner.AddService(typeof (FrameXmlFontService), fontService);
			}
		}

		#region IDisposable Members

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// </summary>
		/// <param name="disposing"></param>
		/// <devdoc>
		/// This method will be called by Visual Studio in reponse
		/// to a package close (disposing will be true in this
		/// case).  The default implementation revokes all
		/// services and calls Dispose() on any created services
		/// that implement IDisposable.
		/// </devdoc>
		protected override void Dispose(bool disposing)
		{
			try
			{
				IServiceContainer serviceContainer = this;
				serviceContainer.RemoveService(typeof (ILayoutControllerService));
				serviceContainer.RemoveService(typeof (LayoutControllerToolWindow));
				serviceContainer.RemoveService(typeof(ILayoutControlManager));

				// Unhook designer event services
				var designerEventService = (IDesignerEventService)GetService(typeof(IDesignerEventService));
				if (designerEventService != null)
					designerEventService.ActiveDesignerChanged -= OnActiveDesignerChanged;

				if (controllerToolWindow != null)
					controllerToolWindow.Dispose();

				if (layoutControlManager != null)
					layoutControlManager.Dispose();

				if (imageService != null)
					imageService.Dispose();

				if (fontService != null)
					fontService.Dispose();
    		}
			catch (Exception ex)
			{
				Trace.WriteLine(ex);
			}
			finally
			{
				base.Dispose(disposing);
			}
		}

		#endregion
	}
}